using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace SimplePoker.Main
{
    public class Round
    {
        private int _Actions = 0;
        private PlayerBase _Player1;
        private PlayerBase _Player2;
        private int _CurrentBet;

        private PlayerBase _NextToAct;

        public PlayerBase NextToAct
        {
            get { return _NextToAct; }
        }

        public bool HasEnded { get; private set; }
        public bool IsShowdown { get; set; }

        public int PotSize { get; private set; }

        public Round(PlayerBase player1, PlayerBase player2)
        {
            if (player1.ChipCount < 0 || player2.ChipCount < 0)
                throw new Exception("A player must have chips left to play.");

            _Player1 = player1;
            _Player2 = player2;
            _NextToAct = player1;
            player1.ChipCount--;
            player2.ChipCount--;
            PotSize = 2;
            var d = new Deck();
            d.Shuffle();
            _Player1.Card = d.TakeCard();
            _Player2.Card = d.TakeCard();
            _CurrentBet = 0;
        }

        public void DoNextAction()
        {
            _Actions++;
            PlayerAction next = PlayerAction.FOLD;

            try
            {
                CallWithTimeout(() => next = _NextToAct.GetAction(), 1000);
            }
            catch (TimeoutException)
            {
                Console.WriteLine(_NextToAct.Name + " timed out and was folded.");
                next = PlayerAction.FOLD;
            }          
                

            if (next == PlayerAction.FOLD && _CurrentBet == 0)
                next = PlayerAction.CALL;

            if (_NextToAct.ChipCount == 0)
                DoShowDown();
            else
            {
                if (_NextToAct.ChipCount == 1)
                    next = PlayerAction.CALL;

                if (next == PlayerAction.CALL)
                {
                    _NextToAct.ChipCount -= _CurrentBet;
                    this.PotSize += _CurrentBet;

                    if (_Actions > 1)
                        DoShowDown();
                }
                else if (next == PlayerAction.BET)
                {
                    if (_CurrentBet == 0)
                    {
                        _NextToAct.ChipCount--;
                        this.PotSize++;
                        _CurrentBet++;
                    }
                    else
                    {
                        _NextToAct.ChipCount -= 2;
                        this.PotSize += 2;
                    }
                }
                else
                {
                    ToggleNextToAct();
                    HasEnded = true;
                    _NextToAct.ChipCount += PotSize;

                }


            }

            ToggleNextToAct();
            _NextToAct.NotifyOpponentsAction(next);
        }

        private void DoShowDown()
        {
            this.IsShowdown = true;
            this.HasEnded = true;
            GiveWinnerPot();
            _Player1.ShowOpponentsCard(_Player2.Card);
            _Player2.ShowOpponentsCard(_Player1.Card);
        }

        private void GiveWinnerPot()
        {
            if (_Player1.Card.Rank > _Player2.Card.Rank)
                _Player1.ChipCount += PotSize;
            else
                _Player2.ChipCount += PotSize;

        }

        private void ToggleNextToAct()
        {
            if (_NextToAct == _Player1)
                _NextToAct = _Player2;
            else
                _NextToAct = _Player1;
        }

        static void CallWithTimeout(Action action, int timeoutMilliseconds)
        {
            Thread threadToKill = null;
            Action wrappedAction = () =>
            {
                threadToKill = Thread.CurrentThread;
                action();
            };

            IAsyncResult result = wrappedAction.BeginInvoke(null, null);
            if (result.AsyncWaitHandle.WaitOne(timeoutMilliseconds))
            {
                wrappedAction.EndInvoke(result);
            }
            else
            {
                threadToKill.Abort();
                throw new TimeoutException();
            }
        }
    }
}
